home *** CD-ROM | disk | FTP | other *** search
/ CD ROM Paradise Collection 4 / CD ROM Paradise Collection 4 1995 Nov.iso / program / swagg_m.zip / MEMORY.SWG / 0004_DPMIINFO.PAS.pas < prev    next >
Pascal/Delphi Source File  |  1993-05-28  |  21KB  |  619 lines

  1. Hello All,
  2.  
  3. Again, interrupts from protected mode. This is an updated version of my
  4. previous article, which, by the way, generated much less respons (none)
  5. than I expected. Where are the BTrieve Programmers, the DesqView API
  6. Writers, the fossil Writers, the .... Maybe they know everything
  7. already. Well then, what has been changed?
  8.  
  9. * little bugs fixed (memory not freed, SEG does not work, etc.)
  10. * I stated that if you want to pass parameters on the stack you had to
  11.   do low level stuff. This is not necessary. I do everything in high
  12.   level(?) pascal now.
  13. * Point 5 of the first Type of unsupported interrupts was inComplete.
  14.   There's sometimes much more work involved :-(
  15. * A simple Unit is presented, which helps to cut down code size. See
  16.   Appendix A
  17.  
  18. Compiling Real to protected mode has been very simple For most of us.
  19. Just Compile and go ahead. 99.5% of your code works fine. But the other
  20. 0.5% is going to give you some hard, hard work.
  21.    In this article I describe first how I first stuck on the protected
  22. stone. Than I try to give a general overview of problems one might
  23. encounter when using interrupts. Next I describe the solutions or give
  24. at least some hints, and I give a solution to the original Program which
  25. made me aware of protected mode conversion problems. Appendix A lists
  26. the code For a Unit I found usefull when porting my DesqView API to
  27. protected mode.
  28.     References can be found at the end of this article. of course, all
  29. disclaimers you can come up With apply!
  30.  
  31.  
  32. When Compiling a big Program, which supported DesqView, a GP fault
  33. occurred. It was simple to trace the bug down: TDX would show me the
  34. offending code. You can get the same error if you try to run the
  35. following Program in protected mode:
  36.  
  37. ========cut here==========================
  38. Program Test;
  39.  
  40. Function  dv_win_me : LongInt;  Assembler;
  41. Asm
  42.   mov    bx,0001h
  43.   mov    ah,12h
  44.   int    15h       {* push dWord handle on stack *}
  45.   pop    ax        {* pop it *}
  46.   pop    dx        {* and return it *}
  47. end;
  48.  
  49. begin
  50.   Writeln(dv_win_me);
  51. end.
  52. ========cut here==========================
  53.  
  54. This little Program must be run under DesqView. When run under DesqView
  55. it returns the current Window handle on the stack. BUT: when Compiled
  56. under protected mode NO dWord handle is returned on the stack. So a
  57. stack fault occurs.
  58.  
  59. What happened? I stuck on one of those unsupported interrupts. Only
  60. supported interupts guarantee to return correct results. You can find a
  61. list of all supported interrupts in the Borland Open Architecture
  62. Handboek For Pascal, Chapter 2 (seperate sold by Borland, not included
  63. in your BP7 package). Supported are the general Dos and Bios interrupts.
  64.  
  65. BeFore eleborating on supported and unsupported interrupts, I have to
  66. explain a few issues which are probably new to us Pascal Programmers.
  67. Whenever a user interrupt occurs in protected mode (you issue a int xx
  68. call) Borlands DPMI Extender switches to Real mode, issues the
  69. interrupt, and switches back to protected mode.
  70.  
  71. This works find For most Cases: interrupts which only pass register
  72. parameters work fine. But what happens if you, For example, called the
  73. Print String Function? (int 21h, ah=09h). You pass as parameters ds:dx
  74. pointing to the String to be printed. But, be aware: in protected mode
  75. ds contains not a segment but a selector! and the selector in ds
  76. probably points to an area above the 1MB boundary. These two things are
  77. going to give Real mode Dos big, big problems. Don't even try it!
  78.     So Borland's DPMI Extender does more than just switching from
  79. protected to Real mode when an interrupt occurs: it translates selectors
  80. to segments when appropriate. But, it can only do so For interrupts it
  81. KNOWS that they need a translation. Such interrupts are called
  82. supported. Interrupts about which Borland's DPMI Extender does not know
  83. about are unsupported. and they are going to give you Real problems!
  84.  
  85. So you see, when only data is passed in Registers, everything works
  86. fine. But if you need to pass Pointers, there is a problem. But why did
  87. the above Program not work? It didn't use selectors you might ask. Well,
  88. there is another set of interrupts that are unsupported: those that
  89. expect or return values on the stack. This is the Case With the above
  90. Program.
  91.  
  92. So, to conclude:
  93. * supported interrupts
  94.   - simple parameter passing using Registers, no segments/selectors
  95.     or stacks included
  96.   - interrupts which Borland's DPMI Extender knows about (too few For
  97.     most of us)
  98. * unsupported interrupts
  99.   - using segments/selectors
  100.   - involving stacks
  101.  
  102. In the next two sections I will fix both Types of problems. I make use
  103. of the DPMI Unit, which comes With the Open Architecture Handbook. You
  104. do not need this Unit. As this DPMI Unit is just a wrapper around the
  105. DPMI interrupt 31h, simply looking the interrupts up in Ralph Brown's
  106. interrupts list and writing Functions/Procedures For them, works fine.
  107.  
  108.  
  109. Unsupported interrupts which need segments
  110. ------------------------------------------
  111.  
  112. Because the data segment and stack segment reside in protected mode, you
  113. need to allocate memory in Real mode, copy your data (which resides
  114. above 1MB) and issue the interrupt by calling the DPMI Simulate Real
  115. Interrupt. So our to-do list is:
  116. 1) allocate Real mode memory
  117. 2) copy data from protected mode to Real mode
  118. 3) set up the Real mode Registers
  119. 4) issue interrupt
  120. 5) examine results
  121.  
  122. 1) You can allocate Real mode memory by issueing a GlobalDosAlloc (not
  123.    referenced in the online help, but you can look it up in the
  124.    Programmer's refercence manual) request. The GlobalDosAlloc is in the
  125.    WinApi Unit. For example:
  126.  
  127.      Uses WinAPI;
  128.      Var
  129.        Return : LongInt;
  130.        MemSize : LongInt;
  131.      begin
  132.        MemSize := 1024;
  133.        Return := GlobalDosAlloc(MemSize);
  134.      end;
  135.  
  136.    This call allocates a block of memory, 1K in size, below the 1MB
  137.    boundary. The value in Return should be split in LongRec(Return).Lo
  138.    and LongRec(Return).Hi. The Hi-order Word contains the segment base
  139.    address of the block. The low-order Word contains the selector For
  140.    the block.
  141.  
  142. 2) You use the selector to acces the block from protected mode and you
  143.    use the segment of the block to acces the block within Real mode (your
  144.    interrupt).
  145.        For example: we want to exchange messages With some interrupt. The
  146.    code For this would be:
  147.      Uses WinAPI;
  148.      Var
  149.        Return : LongInt;
  150.        MemSize : LongInt;
  151.        RealModeSel : Pointer;
  152.        RealModeSeg : Pointer;
  153.        Message : String;
  154.      begin
  155.        MemSize := 256;
  156.        Return := GlobalDosAlloc(MemSize);
  157.        PtrRec(RealModeSel).seg := LongRec(Return).Lo;
  158.        PtrRec(RealModeSel).ofs := 0;
  159.        PtrRec(RealModeSeg).seg := LongRec(Return).Hi;
  160.        PtrRec(RealModeSeg).ofs := 0;
  161.  
  162.      {* Both RealModeSel(ector) and RealModeSeg(ment) point to the same
  163.  
  164.         physical address now. *}
  165.  
  166.      {* move message from protected mode memory to the allocated selector *}
  167.        Message := 'How are your?';
  168.        Move(Message, RealModeSel^, Sizeof(Message));
  169.  
  170.      {* issue interupt, explained below *}
  171.      { <..code..> }
  172.      {* the interrupt returns a message *}
  173.  
  174.      {* move interrupt's message below 1MB to protected mode *}
  175.        Move(RealModeSel^, Message, Sizeof(Message));
  176.        Writeln(Message);     {* "yes, I'm fine. Thank you!" *}
  177.      end;
  178.  
  179. 3) We will now examine how to setup an interrupt For Real mode. Most of
  180.    the time this is transparantly done by Borland's DPMI Extender, but
  181.    we are on our own now. to interrupt Dos, we use the DPMI Function
  182.    31h, 0300h. This interrupt simulates an interrupt in Real mode.
  183.  
  184.    The Simulate Real Mode Interrupt Function needs a Real mode register
  185.    data structure. We pass the interrupt and the Real mode register data
  186.    structure to this Function, which will than start to simulate the
  187.    interrupt.
  188.        This Function switches to Real mode, copies the contents of the
  189.    data structure into the Registers, makes the interrupt, copies the
  190.    Registers back into the supplied data structure, switches the
  191.    processor back to protected mode and returns. Voila: you are in
  192.    control again.
  193.        Maybe you ask: why need I to setup such a data structure? Why can
  194.    I not simply pass Registers? Several reasons exist, but take For
  195.    example the RealModeSeg of the previous example. You cannot simply
  196.    load a RealModeSeg in a register. Most likely a segment violation
  197.    would occur (referring to a non existing segment or you do not have
  198.    enough rights etc.). ThereFore only in Real mode can Real mode
  199.    segments be loaded.
  200.  
  201.        The data structure to pass Registers between protected and Real
  202.    mode can be found in the DPMI Unit which I Repeat here:
  203.  
  204.      Type
  205.        TRealModeRegs = Record
  206.          Case Integer of
  207.            0: (
  208.                EDI, ESI, EBP, EXX, EBX, EDX, ECX, EAX: LongInt;
  209.                Flags, ES, DS, FS, GS, IP, CS, SP, SS: Word);
  210.            1: (
  211.                DI,DIH, SI, SIH, BP, BPH, XX, XXH: Word;
  212.                Case Integer of
  213.                  0: (
  214.                      BX, BXH, DX, DXH, CX, CXH, AX, AXH: Word);
  215.  
  216.                  1: (
  217.                      BL, BH, BLH, BHH, DL, DH, DLH, DHH,
  218.                      CL, CH, CLH, CHH, AL, AH, ALH, AHH: Byte));
  219.          end;
  220.  
  221.    This looks reasonably Complex, doesn't it! More simply is the
  222.    following structure (found in, For example, "Extending Dos" by Ray
  223.    Duncan e.a.)
  224.    offset  Lenght Contents
  225.    00h     4      DI or EDI
  226.    04h     4      SI or ESI
  227.    08h     4      BP or EBP
  228.    0Ch     4      reserved, should be zero
  229.    10h     4      BX or EBX
  230.    14h     4      DX or EDX
  231.    18h     4      CX or ECX
  232.    1Ch     4      AX or EAX
  233.    20h     2      CPU status flags
  234.    22h     2      ES
  235.    24h     2      DS
  236.    26h     2      FS
  237.    28h     2      GS
  238.    2Ah     2      IP (reserved, ignored)
  239.    2Ch     2      CS (reserved, ignored)
  240.    2Eh     2      SP (ignored when zero)
  241.    30h     2      SS (ignored when zero)
  242.  
  243.    In the following example, I set the Registers For the above message
  244.    exchanging Function. It's best to clear all Registers (or at least
  245.    the SS:SP Registers) beFore calling the Simulate Real Mode Interrupt.
  246.  
  247.      Uses DPMI;
  248.      Var
  249.        Regs : TRealModeRegs;
  250.      begin
  251.        FillChar(Regs, Sizeof(TRealModeRegs), #0); {* clear all Registers *}
  252.  
  253.        With Regs do  begin
  254.          ah := $xx;
  255.          es := PtrRec(RealModeSeg).Seg;
  256.          di := PtrRec(RealModeSeg).ofs
  257.        end;  { of With }
  258.      end;
  259.  
  260.    All this is fairly standard. Just set up the Registers you interrupts
  261.    expect, very much like the Intr Procedure.
  262.  
  263. 4) We can now issue the interrupt in Real mode using the RealModeInt
  264.    Procedure (in the DPMI Unit). Its definition is
  265.  
  266.      Procedure RealModeInt(Int: Byte; Var Regs: TRealModeRegs);
  267.  
  268.    or you can call int 31h, Function 0300h, see Ralph Brown's interrupt
  269.    list.
  270.    For our message exchanging Program it would simply be:
  271.      RealModeInt(xx, Regs);
  272.  
  273. 5) Examine the results. Modified Registers are passed in the Regs data
  274.    structure so you can check the results.
  275.        It is necessary to discriminate between to Types of returned
  276.    segments. In the example above, I assumed that the Interrupt returned
  277.    data in the allocated memory block. I already have a selector For
  278.    that block, so I can examine the results.
  279.    Another Type of interrupt returns Pointers to segments it has
  280.    allocated itself. As we don't have a selector For that memory block
  281.    we have to create one. We need the following Functions:
  282.    - AllocSelectors, to allocate a selector
  283.    - SetSelectorBase, to let it point to a physical address
  284.    - SetSelectorLimit, to set the size
  285.    An example For this situation: Assume that a certain interrupt
  286.    returns a Pointer to a memory area. This Pointer is in es:di.
  287.    Register cs contains the size of that memorya rea. I show you how to
  288.    acces that segment.
  289.  
  290.      Uses DPMI;
  291.      Var
  292.        Regs : TRealModeRegs;
  293.        p : Pointer;
  294.      begin
  295.      {* setup  Regs *}
  296.      {* issue interrupt, returning es:di *}
  297.  
  298.      {* as we don't have a selector, create one *}
  299.        PtrRec(p).Seg := AllocSelectors(1);
  300.        PtrRec(p).ofs := 0;
  301.  
  302.      {* this selector points to no physical address and has size 0 *}
  303.      {* so let the selector point to es:di *}
  304.        SetSelectorBase(PtrRec(p).Seg, Regs.es*16+Regs.di);
  305.  
  306.      {* Forgive me! This was a joke. The last statement does not work   *}
  307.      {* of course. Regs.es*16+Regs.di will in the best Cases ({$R+,Q+}) *}
  308.      {* result in an overflow error. You have to Write:                 *}
  309.        SetSelectorBase(PtrRec(p).Seg, Regs.es*LongInt(16)+Regs.di);
  310.  
  311.      {* the selector now points to a memory area of size 0 *}
  312.        SetSelectorLimit(PtrRec(p).Seg, Regs.cx);
  313.  
  314.      {* we don't have to set the accesrights (code/data, read/Write, etc. *}
  315.      {* as they are almost ok *}
  316.  
  317.      {* we can now acces this memory using selector p *}
  318.      { <acces block> }
  319.  
  320.      {* after using it, free selector *}
  321.        FreeSelector(PtrRec(p).Seg);
  322.      end;
  323.  
  324.  
  325. Are there any questions? No? Let's go ahead than to the next Type of
  326. interrupts.
  327.  
  328. Unsupported interrupts which use the stack
  329. ------------------------------------------
  330. The second Type of unsupported interrupts are the ones which make use of
  331. the stack. We can distinguish between:
  332. 1. interrupts which need parameters on the stack
  333. 2. interrupts which return parameters on the stack
  334.  
  335. 1) For the first Type we need to setup a stack. There is an extra
  336.    Compilication, which I had not told yet. As the stack in protected
  337.    mode resides in a protected mode segment it is unusable For the Real
  338.    mode interrups. So Borland's DPMI Extender switches from the
  339.    protected to a Real mode stack (and back). We can supply a default
  340.    Real mode stack if we set the stack Registers (ss and sp) in the Real
  341.    mode register data structure to zero. else it is assumed that ss:sp
  342.    points to a Real mode stack. Failure to set them up properly could
  343.    have disastrous results!
  344.  
  345.    We will have to do:
  346.    1) create a Real mode stack using GlobalDosAlloc
  347.    2) fill this stack With values
  348.    3) set ss and sp properly
  349.    4) issue interrupt
  350.  
  351.    All in one example Program. The following Program sets DesqView's
  352.    mouse on a given location on the screen. The supplied handle is the
  353.    handle of the mouse. As DesqView needs dWord values on the stack I
  354.    allocated a LongIntArray stack which is defined as:
  355.  
  356.      Const
  357.        MaxLongIntArray = 1000;
  358.      Type
  359.        PLongIntArray = ^TLongIntArray;
  360.        TLongIntArray = Array [0..MaxLongIntArray] of LongInt;
  361.  
  362.    The example Program:
  363.  
  364.      Procedure SetMouse(Handle, x, y : LongInt);
  365.      Const
  366.        StackSize = 3*Sizeof(LongInt);
  367.      Var
  368.        Regs : TRealModeRegs;
  369.        Stack : PLongIntArray;
  370.        l : LongInt;
  371.      begin
  372.      {* clear all Registers *}
  373.        FillChar(Regs, Sizeof(TRealModeRegs), 0);
  374.  
  375.      {* setup the Registers *}
  376.        Regs.ax := $1200;
  377.        Regs.bx := $0500;
  378.  
  379.      {* allocate the stack *}
  380.        l := GlobalDosAlloc(StackSize);
  381.  
  382.      {* set stacksegment register sp. ss should be set to the bottom of *}
  383.      {* the stack = 0 *}
  384.        Regs.sp := LongRec(l).Hi;
  385.        Stack := Ptr(LongRec(l).Lo, 0);
  386.  
  387.      {* fill the stack *}
  388.        Stack^[0] := Handle;
  389.        Stack^[1] := y;
  390.        Stack^[2] := x;
  391.  
  392.      {* issue the interrupt *}
  393.        RealModeInt($15, Regs);
  394.  
  395.      {* free the stack *}
  396.        GlobalDosFree(PtrRec(Stack).Seg);
  397.      end;
  398.  
  399. 2) Looks much like solution above. if only values are returned on the
  400.    stack. Don't Forget to set sp to the top of the stack. In the above
  401.    example settings Regs.sp := StackSize;
  402.        An example is given below, where a solution to my original
  403.    problem is given.
  404.  
  405.  
  406. Solution For the dv_win_me Procedure:
  407.  
  408.   Uses DVAPI, Objects, WinApi, WinTypes, DPMI;
  409.  
  410.   Function dv_win_me : LongInt;
  411.   Const
  412.     StackSize = Sizeof(LongInt);
  413.   Var
  414.     Regs : TRealModeRegs;
  415.     RealStackSeg : Word;
  416.     RealStackSel : Word;
  417.     l : LongInt;
  418.   begin
  419.   {* clear all Registers *}
  420.     FillChar(Regs, Sizeof(TRealModeRegs), #0);
  421.  
  422.   {* allocate a 1 dWord stack *}
  423.     l := GlobalDosAlloc(StackSize);
  424.     RealStackSeg := LongRec(l).Hi;
  425.     RealStackSel := LongRec(l).Lo;
  426.  
  427.   {* clear the stack (not necessary) *}
  428.     FillChar(Ptr(RealStackSel, 0)^, StackSize, #0);
  429.  
  430.   {* set Registers *}
  431.     With Regs do  begin
  432.       bx := $0001;
  433.       ah := $12;
  434.       ss := RealStackSeg;
  435.       sp := StackSize;
  436.     end;  { of With }
  437.  
  438.   {* perForm Real mode interrupt *}
  439.     RealModeInt($15, Regs);
  440.     dv_win_me := PLongInt(Ptr(RealStackSel, 0))^;
  441.  
  442.   {* free the stack *}
  443.     GlobalDosFree(PtrRec(RealStackSel).Seg);
  444.   end;
  445.  
  446.   begin
  447.     Writeln(dv_win_me);
  448.   end.
  449.  
  450.  
  451. You see, code size bloats in protected mode! (ThereFore Borland gave us
  452. 16MB....)
  453.  
  454.  
  455. Appendix A.
  456. -----------
  457.  
  458. As promised, some routines I found usefull when working With Real mode
  459. segments.
  460.  
  461. ====================cut here====================
  462. Unit DPMIUtil;
  463.  
  464. Interface
  465.  
  466. Uses Objects, DPMI;
  467.  
  468. Const
  469.   MaxLongIntArray = 1000;
  470. Type
  471. {* this Type is usefull For DesqView stacks *}
  472.   PLongIntArray = ^TLongIntArray;
  473.   TLongIntArray = Array [0..MaxLongIntArray] of LongInt;
  474.  
  475.  
  476. {* clear all Registers to zero *}
  477.  
  478. Procedure ClearRegs(Var Regs : TRealModeRegs);
  479.  
  480. {* allocate memory using GlobalDosAlloc and split the returned *}
  481. {* LongInt into a protected mode Pointer and a Real mode segment *}
  482.  
  483. Function  XGlobalDosAlloc(Size : LongInt; Var RealSeg : Word) : Pointer;
  484.  
  485. {* free memory *}
  486.  
  487. Procedure XGlobalDosFree(p : Pointer);
  488.  
  489.  
  490. Implementation
  491.  
  492. Uses WinAPI;
  493.  
  494. Procedure ClearRegs(Var Regs : TRealModeRegs);
  495. begin
  496.   FillChar(Regs, Sizeof(TRealModeRegs), 0);
  497. end;
  498.  
  499. Function  XGlobalDosAlloc(Size : LongInt; Var RealSeg : Word) : Pointer;
  500. Var
  501.   l : LongInt;
  502. begin
  503.   l := GlobalDosAlloc(Size);
  504.   RealSeg := LongRec(l).Hi;
  505.   XGlobalDosAlloc := Ptr(LongRec(l).Lo, 0);
  506. end;
  507.  
  508. Procedure XGlobalDosFree(p : Pointer);
  509. begin
  510.   GlobalDosFree(PtrRec(p).Seg);
  511. end;
  512.  
  513. end.  { of Unit DPMIUtil }
  514. ====================cut here====================
  515.  
  516.  
  517. Example code how to use it. The above dv_win_me routine would look like:
  518.  
  519.   Uses DVAPI, Objects, WinApi, WinTypes, DPMI;
  520.  
  521.   Function dv_win_me : LongInt;
  522.   Const
  523.     StackSize = Sizeof(LongInt);
  524.   Var
  525.     Regs : TRealModeRegs;
  526.     Stack : PLongIntArray;
  527.   begin
  528.   {* clear all Registers *}
  529.     ClearReges(Regs);
  530.  
  531.   {* allocate a 1 dWord stack *}
  532.     Stack := XGlobalDosAlloc(StackSize, Regs.ss);
  533.  
  534.   {* set Registers *}
  535.     Regs.bx := $0001;
  536.     Regs.ah := $12;
  537.     Regs.sp := StackSize;
  538.  
  539.   {* perForm Real mode interrupt *}
  540.     RealModeInt($15, Regs);
  541.     dv_win_me := Stack^[0];
  542.  
  543.   {* free the stack *}
  544.     XGlobalDosFree(Stack);
  545.   end;
  546.  
  547.   begin
  548.     Writeln(dv_win_me);
  549.   end.
  550.  
  551. Compare this to the previous code. It just looks a bit prettier
  552. according to my honest opininion.
  553.  
  554.  
  555. Conclusion
  556. ----------
  557.  
  558. As you saw, the switch from Real to protected mode may be rather
  559. painfull. I hope With the above examples and explanations you can make
  560. it a bit more enjoyable. One question remains: why did Borland not
  561. clearly told us so? Why not present a few examples, warnings, etc.?
  562. Maybe RiChard Nelson can answer this questions For us. Everything he
  563. says is his private opinion of course, but a look in the kitchen could
  564. be worthWhile.
  565.  
  566. if you still have questions, I'm willing to answer them in either
  567. usenet's Comp.LANG.PASCAL or fidonet's PASCAL.028 or PASCAL. I can't
  568. port your library of course but if the inFormation presented here is not
  569. enough, just ask.
  570.  
  571.  
  572.  
  573. References
  574. ----------
  575. - The usual Borland set of handbooks
  576.  
  577. - "Borland Open Architecture Handbook For Pascal", sold separately by
  578. Borland,
  579.   184 pages.
  580.  
  581. - "Extending Dos, a Programmer's Guide to protected-mode Dos", Ray
  582.   Duncan, Charles Petzold, andrew Schulman, M. Steven Baker, Ross P.
  583.   Nelson, Stephen R. Davis and Robert Moote. Addison-Wesly, 1992.
  584.   ISBN: 0-201-56798-9
  585.  
  586. - "PC Magazine Programmer's Technical Reference: The Processor and
  587.    Coprocessor", Robert L. Hummel. Ziff-Davis Press, 1992.
  588.   ISBN: 1-56276-016-5
  589.  
  590.  
  591.   { Dunno if this came before or after this message :) }
  592.  
  593. Hello Protectors,
  594.  
  595. of course, a few hours after my message has been released to the net,
  596. bugfixes seem necessary )-:
  597.  
  598. Some minor bugfixes:
  599.  
  600. * In the example about allocating memory below the 1MB, memory is
  601.   allocated but not released. As we have only 1MB down their, this can
  602.   become a problem ;-)
  603.   Fix:  adding the statement
  604.     GlobalDosFree(RealModeSel);
  605.   will clean things up
  606.  
  607.  * The solution to interrupts which requires parameters passed on the
  608.   stack has a bug. The
  609.     les  di,Regs
  610.   statement does not work of course. Replace by
  611.     mov  di,ofFSET Regs
  612.     mov  dx,SEG Regs
  613.     mov  es,dx
  614.   This does not work when Regs is declared in the stack segment (well
  615.   done Borland....), you encounter bug number 16, just as I did.... (see
  616.   next message)
  617.  
  618.  
  619.